package tomkitty.server;

import tomkitty.turdlet.*;
import tomkitty.util.*;
import java.io.*;
import java.net.*;

/**
 *
 * <code>ConnectionHandler</code> takes care of handling requests for the 
 * <code>Server</code> class. The http header is examined and the requested 
 * resource is returned, if
 * possible. If the resource is the name of a turdlet, then a turdlet of
 * that type is instantiated and connected to the input and output buffers,
 * and all IO is then handled by the turdlet. Otherwise, if no turdlet can
 * be found to correspond with the resource name or if an exception is
 * thrown somewhere, an <code>ErrorTurdlet</code> will be instantiated 
 * to return an
 * appropriate response. Currently only the GET and HEAD HTTP methods are
 * implemented, but POST will be included eventually.
 *
 * @author alan, micha
 */
public class ConnectionHandler extends Thread implements HttpConstants {

	Socket sin = null;
	ConnectionHandlerPool pool = null;

	/**
	 *
	 * Sets the socket this handler is waiting on. This method will wake up
	 * this sleeping thread if the socket parameter is not null.
	 *
	 * @param s incoming socket connection. Must be the result of a call to
	 * 					<code>accept</code> or similar.
	 */
	public synchronized void setSocket(Socket s) {
		sin = s;
		notify();
	}

	/**
	 *
	 * Sets the <code>ConnectionHandlerPool</code> that this object belongs to. 
	 * This method is really only meant to be used by the 
	 * <code>ConnectionHandlerPool</code> class.
	 *
	 * @param p the pool the object belongs to.
	 */
	public synchronized void setPool(ConnectionHandlerPool p) {
		pool = p;
	}

	public Turdlet errorTurdlet(BufferedReader in, HttpPrintStream out, 
														HttpRequest req, String status) {

		ErrorTurdlet et = new ErrorTurdlet();

		et.init(in, (HttpPrintStream) out, req, null, this);
		et.setErr(status);
		et.setErrNo(status);
		et.doit();

		return (Turdlet) et;
	}

	public Turdlet NotFoundError(BufferedReader in, HttpPrintStream out, 
														HttpRequest req) {
		return errorTurdlet(in, out, req, HTTP_NOT_FOUND);
	}

	public Turdlet InternalError(BufferedReader in, HttpPrintStream out, 
														HttpRequest req) {
		return errorTurdlet(in, out, req, HTTP_INTERNAL_ERROR);
	}

	public Turdlet ServerError(BufferedReader in, HttpPrintStream out, 
														HttpRequest req) {
		return errorTurdlet(in, out, req, HTTP_SERVER_ERROR);
	}

	public Turdlet BadRequestError(BufferedReader in, HttpPrintStream out, 
														HttpRequest req) {
		return errorTurdlet(in, out, req, HTTP_BAD_REQUEST);
	}

	/**
	 *
	 * Handles the connection.
	 */
	public synchronized void run() {
		while (true) {

			// wait until a socket is assigned to sin

			try {
				if (sin == null) {
					wait();
				}
			} catch(InterruptedException e) {
				e.printStackTrace();	
			}

			// socket is assigned. it will already be connected and ready.

			try {

				BufferedReader in =
					new BufferedReader(new InputStreamReader(sin.getInputStream()));
				HttpPrintStream out = new HttpPrintStream(sin.getOutputStream());
				
				// grab header
				String firstLine = in.readLine();

				// grab the rest
				String body = "";
				while(in.ready()) {
					body = body + in.readLine();
				}

				String resourceName = "";
				String fullPath = "";
				HttpRequest req = null;
				
				try {

					System.out.println("req: '" + firstLine + "'");

					req = new HttpRequest(firstLine, body);

					// FIXME: this code doesn't really belong here but for now this 
					// is where it is

					if (! req.isValid()) {
						resourceName = "bad_request";
					} else if (req.getHeader().method.equals("HEAD")) {
						out.sendHeadersOnly(true);
					} else if (! req.getHeader().method.equals("GET")) {
						resourceName = "not_implemented";
					} else {
						resourceName = req.getHeader().uri.getPath();

						// remove the leading '/' if there is one
						if (resourceName.charAt(0) == '/') {
							resourceName = resourceName.substring(1);
						}

						// save the full path in case we will serve a static file
						fullPath = resourceName;

						// remove all segments following the turdlet name
						int i = resourceName.indexOf('/');
						if (i >= 0) {
							resourceName = resourceName.substring(0, i);
						}

					}

				} catch(Exception e) {

					// getting NullPointerExceptions in the regex class for some reason.
					// i suspect it's because Matcher isn't a thread-safe class

					System.out.println("OMG somfeeng happeng");
					e.printStackTrace();
					resourceName = "internal_server_error";

				}

				Turdlet theTurd;

				if(resourceName.equals("bad_request")) {
						
					// the header was not well-formed

					theTurd = BadRequestError(in, out, req);

				} else if (resourceName.equals("not_implemented")) {

					// this server has not implemented that method

					theTurd = InternalError(in, out, req);

				} else if (resourceName.equals("internal_server_error") ||
										resourceName.equals("")) {

					// there was an exception thrown while parsing the request headers
					// or something is screwed up somewhere

					theTurd = ServerError(in, out, req);

				} else {

					// headers are ok and resourceName was set

					try {

						// create and instance of the turdlet by name. for example, if
						// resourceName == "OmgWtf", then the class app.OmgWtf will
						// be instantiated. if no such turdlet exists there will be a
						// ClassNotFoundException thrown.

						theTurd = 
							(Turdlet) Class.forName("app."+resourceName).newInstance();

						theTurd.init(in, (HttpPrintStream) out, req, null, this);
						theTurd.doit();

					} catch (ClassNotFoundException e) {
								
						// class not found
						// we load a turdlet here that tries to interpret the
						// resourceName as a static html file to regurgitate and if
						// the file doesn't exist then the static loader turdlet
						// will delegate to the not found error turdlet

						StaticTurdlet st = new StaticTurdlet(fullPath);
						st.init(in, out, req, null, this);
						st.doit();

						theTurd = (Turdlet) st;

					} catch (Exception e) {

						// some other problem instantiating the turdlet. maybe the
						// app turdlet is messed up in some way or something.

						e.printStackTrace();
						theTurd = ServerError(in, out, req);
					}

				}
				
				// wait until the turdlet is done. the turdlet might be waiting for
				// an event or something after its doit() method has finished.

				while (! theTurd.isDone()) {
					// just chill till it's done
				}
				
				sin.close();
				sin = null;

				// try to reuse this thread. pool has to be explicitly set after
				// this class is instantiated for recycle() to actually do anything.

				if (pool != null) {
					pool.recycle(this);
				}

			} catch(IOException e) {

				e.printStackTrace();

			}
		}
	}

}
